#pragma once

/**
 * EventLoop:一个线程一个EventLoop，负责事件循环和任务调度
 * 如果这个描述符，在多个线程中都触发了事件，进行处理,就会存在线程安全的问题，
 * 因此我们需要将一个连接的事件监控，以及连接事件处理，以及其它操作都放在同一个线程中进行 --- one loop per thread
 * 如何保证一个连接的所有操作都在EventLopp对应的线程中:
 * 解决方案:给EventLoop模块中，添加一个任务队列，对连接的所有操作,都进行-次封装,将对连接的操作并不直接执行，而是当作任务添加到任务队列中
 */

/**
 * 用户要监控一个文件描述符不需要自己去调用 Poller 模块，
 * 而是通过调用 EventLoop 模块中的接口，由 EventLoop 去调用 Poller 中的相关接口，完成事件监控。
 */

#include "Epoller.h"
#include "Logger.h"
#include "Timer.h"

#include <sys/eventfd.h>

#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>

namespace devem {

using Functor = std::function<void()>;

class EventLoop {
public:
    int CreateWakeupFd() {
        int event_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
        if (event_fd < 0) {
            LOG_WARN("create event_fd fail.");
            abort();
        }
        return event_fd;
    }

    void ReadWakeupFd() {
        uint32_t res = 0;
        ssize_t ret = read(wakeup_fd_, &res, sizeof(uint32_t));
        if (ret < 0) {
            // EINTR -- 被信号打断；EAGAIN -- 表示无数据可读
            if (errno == EINTR || errno == EAGAIN) {
                return;
            }
            LOG_ERROR("read wakeup_fd fail.");
            abort();
        }
    }

    void WriteWakeupFd() {
        uint32_t res = 0;
        ssize_t ret = write(wakeup_fd_, &res, sizeof(uint32_t));
        if (ret < 0) {
            if (errno == EINTR) {
                return;
            }
            LOG_ERROR("read wakeup_fd fail.");
            abort();
        }
    }

    void RunPendingFunctor() {
        std::vector<Functor> temp_tasks;
        // 作用域中加锁，离开作用域自动解锁
        {
            std::unique_lock<std::mutex> lock(mutex_);
            temp_tasks.swap(tasks_);
        }
        for (auto& func_t : temp_tasks) {
            func_t();
        }
    }

    // 用于判断当前线程是否是EventLoop对应的线程
    bool IsInLoop() { return std::this_thread::get_id() == thread_id_; }

    // 在当前loop线程执行cb
    void RunInLoop(const Functor& cb) {
        if (IsInLoop())
            return cb();
        else
            QueueInLoop(cb);
    }

    // 将操作压入任务池
    void QueueInLoop(const Functor& cb) {
        {
            std::unique_lock<std::mutex> lock(mutex_);
            tasks_.push_back(cb);
        }
        // 唤醒有可能因为没有事件就绪，而导致的epoll阻塞；
        // 其实就是给eventfd写入一个数据，eventfd就会触发可读事件
        WriteWakeupFd();
    }

public:
    EventLoop()
        : thread_id_(std::this_thread::get_id()),
          wakeup_fd_(CreateWakeupFd()),
          wakeup_channel_(new Channel(wakeup_fd_, this)) {
        // 设置 wakeup_fd_ 的事件和执行的回调
        // Question: can I reverse these functions postion?(EnableRead,SetReadCallBack)
        wakeup_channel_->SetReadCallBack(std::bind(&EventLoop::ReadWakeupFd, this));
        wakeup_channel_->EnableRead();
    }

    /* 三步走:事件监控 -> 就绪事件处理 -> 执行任务 */
    void start() {
        // Epoller 进行事件监听，上报给 EventLoop，EventLoop通知Channel处理响应时间
        std::vector<Channel*> active_channels;
        for (;;) {
            // #1. 事件监控
            epoller_.Wait(&active_channels, 0);
            // #2. 就绪事件处理
            for (auto& channel : active_channels) {
                channel->HandleEvent();
            }
            // #3. 执行任务
            // 执行任务池任务
            RunPendingFunctor();
        }
    }

    // 添加/修改描述符的事件监控, Channel and Epoller
    void UpdateEvent(Channel* channel) { return epoller_.UpdateEvent(channel); }
    // 移除描述符的监控
    void RemoveEvent(Channel* channel) { return epoller_.RemoveEvent(channel); }

    ~EventLoop() {
        if (wakeup_fd_)
            close(wakeup_fd_);
    }

private:
    std::thread::id thread_id_;
    int wakeup_fd_;                            // mainloop获取到新channel，用来唤醒subloop(轮询算法)
    std::unique_ptr<Channel> wakeup_channel_;  // 绑定 wakeu_fd
    Epoller epoller_;
    std::vector<Functor> tasks_;
    std::mutex mutex_;
    // Timer timer_;
    // std::vector<Channel*> channels_;
};

/*
EventLoop模块在实例化对象的时候，必须在线程内部
EventLoop实例化对象时会设置自己的thread_
id,如果我们先创建了多个EventLoop对象,然后创建了多个线程，将各个线程的id,重新给EventLoop进行设置
存在的问题:在构造EventLoop对象，到设置新的thread id期间将是不可控的
因此我们必先创建线程，在线程的入口函数中，实例化EventLoop对象
*/

class LoopThread {
private:
    // Producer:
    void EntryThread() {
        EventLoop loop;
        {
            std::unique_lock<std::mutex> lock(mutex_);
            loop_ = &loop;
            cond_.notify_all();
        }
    }

public:
    LoopThread() : loop_(nullptr), thread_(std::thread(&LoopThread::EntryThread, this)) {}

    // Consumer:
    EventLoop* SelfEventLoop() {
        EventLoop* loop;
        {
            std::unique_lock<std::mutex> lock(mutex_);
            cond_.wait(lock, [&]() { return loop_ == nullptr; });
            loop = loop_;
        }
        return loop;
    }

private:
    std::thread thread_;
    EventLoop* loop_;
    std::mutex mutex_;              // 互斥锁
    std::condition_variable cond_;  // 条件变量
};
}  // namespace devem
